/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

/* includes (local) ----------------------------------------------------------*/
#include "k_obj.h"
#include "k_cls.h"
#include "k_task.h"
#include "k_critical.h"
/* includes (standard library, system) ---------------------------------------*/
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
/* includes (other library) --------------------------------------------------*/
/* includes (project) --------------------------------------------------------*/
#include <x_it.h>
#include <x_warn.h>

#include <m_memtry.h>

#include <a_atomic.h>

/* defines -------------------------------------------------------------------*/
/* typedefs ------------------------------------------------------------------*/
/* macro ---------------------------------------------------------------------*/
/* variables (extern) --------------------------------------------------------*/
/* variables (global) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/
/* inlines -------------------------------------------------------------------*/
/* functions -----------------------------------------------------------------*/

static __always_inline obj_id __obj_alloc(cls_id c)
{
    if (c->obj_ops->alloc != NULL) {
        return c->obj_ops->alloc();
    } else {
        return (obj_id)malloc(c->obj_size);
    }
}

static __always_inline void __obj_free(cls_id c, obj_id obj)
{
    if (c->obj_ops->free != NULL) {
        c->obj_ops->free(obj);
    } else {
        free(obj);
    }
}

/* check if protection is not needed */

static __always_inline bool __obj_no_protect(void)
{
    return it_context() || in_critical();
}

static int __obj_verify(cls_id c, obj_id obj)
{
    uintptr_t magic;

#if UINTPTR_MAX == UINT_MAX
    int order = 2;
#else
    int order = 1;   // FIXME
#endif

    if (unlikely(mem_try((void *)&magic, (void *)&obj->magic, order)) != 0) {
        errno = ERRNO_OBJ_ILLEGAL_ID;
        return -1;
    }

    if (unlikely(magic != (uintptr_t)obj)) {
        errno = ERRNO_OBJ_ILLEGAL_MAGIC;
        return -1;
    }

    if (unlikely(obj->cls != c)) {
        errno = ERRNO_OBJ_ILLEGAL_CLASS;
        return -1;
    }

    return 0;
}

/**
 * obj_verify - verify if an object is valid
 * @c:     the class this object should belongs to
 * @obj:   the object id
 *
 * can only be invoked when in "nosleep" context, if not, use obj_verify_protect
 * instead
 *
 * return: 0 on success, negative value on error
 */
int obj_verify(cls_id c, obj_id obj)
{
#ifndef __HOST__
    WARN_ON(__obj_verify(c, obj) != 0, return -1, "Invalid object ID, errno = %p!", errno);
#endif

    return 0;
}

/**
 * obj_verify_protect - verify if an object is valid and protect it for deletion
 * @c:     the class this object should belongs to
 * @obj:   the object id
 *
 * return: 0 on success, negative value on error
 */
int obj_verify_protect(cls_id c, obj_id obj)
{
    int ret = 0;

    /* interrupt context and critical context needless prevent for deletion */

    if (unlikely(__obj_no_protect())) {
        return obj_verify(c, obj);
    }

    task_lock();

    if (unlikely(obj_verify(c, obj) != 0)) {
        ret = -1;
    } else {
        /* must be an object, we can write of cause */

        atomic_uint_inc(&obj->safe_cnt);
    }

    task_unlock();

    return ret;
}

/**
 * obj_unprotect - unprotect an object
 * @obj:   the object id
 *
 * return: NA
 */
void obj_unprotect(obj_id obj)
{
    if (unlikely(__obj_no_protect())) {
        return;
    }

    atomic_uint_dec(&obj->safe_cnt);

    if (obj->task_del != NULL && atomic_uint_get(&obj->safe_cnt) == 0) {
        task_resume((task_id)obj->task_del);
    }
}

static int __obj_init(cls_id c, obj_id obj, const char *name, va_list valist, unsigned int flags)
{
    if (unlikely(c->obj_ops->init(obj, valist) != 0)) {
        return -1;
    }

    obj->cls = c;
    obj->flags = flags;
    obj->name = name;
    obj->task_del = NULL;

    atomic_uint_set(&obj->safe_cnt, 0);

    atomic_uint_set(&obj->open_cnt, 1);

    if (unlikely(mutex_lock(&c->lock) != 0)) {
        return -1;
    }

    dlist_add(&c->objs, &obj->node);

    obj->magic = (uintptr_t)obj;

    (void)mutex_unlock(&c->lock);

    return 0;
}

static int __obj_eq(obj_id obj, va_list valist)
{
    /* return -1 (fail) so the iteration will stop and return this object */

    return obj == va_arg(valist, obj_id) ? -1 : 0;
}

static obj_id __obj_inited(cls_id class, obj_id obj)
{
    return obj_foreach(class, __obj_eq, obj);
}

/**
 * obj_init - initialize an object
 * @c:     the class this object should belongs to
 * @obj:   the object id
 *
 * return: 0 on success, negative value on error
 */
int obj_init(cls_id c, obj_id obj, ...)
{
    int ret;
    va_list valist;

    char *obj_start = (char *)obj;
    char *obj_end = obj_start + sizeof(obj_t) - 1;

    if (__obj_verify(c, obj) == 0 && __obj_inited(c, obj) != NULL) {
        errno = ERRNO_OBJ_ILLEGAL_OPERATION;
        WARN("Trying to initialize an initialized object!");
        return -1;
    }

    if (errno == ERRNO_OBJ_ILLEGAL_ID) {
        WARN("Object can not be accessed!");
        return -1;
    }

    if (mem_try((void *)obj_start, (void *)obj_start, 0) != 0 ||
        mem_try((void *)obj_end, (void *)obj_end, 0) != 0) {
        WARN("Object can not be accessed!");
        return -1;
    }

    va_start(valist, obj);

    ret = __obj_init(c, obj, NULL, valist, OBJ_FLAGS_STATIC);

    va_end(valist);

    return ret;
}

/**
 * obj_create - create an object
 * @c:     the class this object should belongs to
 * @obj:   the object id
 *
 * return: object id on success, NULL on error
 */
obj_id obj_create(cls_id c, ...)
{
    va_list valist;
    obj_id obj = __obj_alloc(c);

    if (unlikely(obj == NULL)) {
        return NULL;
    }

    va_start(valist, c);

    if (unlikely(__obj_init(c, obj, NULL, valist, 0) != 0)) {
        __obj_free(c, obj);

        obj = NULL;
    }

    va_end(valist);

    return obj;
}

/**
 * obj_open - open a object with a given name
 * @c:     the class id
 * @name:  the name of the object
 * @oflag: flag, 0 or O_CREAT or O_CREAT | O_EXCL
 * @value: if oflag has O_CREAT option
 *
 * return: the allocated obj or NULL on error
 */
obj_id obj_open(cls_id c, const char *name, int oflag, va_list valist)
{
    obj_id obj;

    WARN_ON(name == NULL, errno = ERRNO_OBJ_ILLEGAL_OPERATION; return NULL, "Invalid name!");

    obj = obj_find(c, name);

    if (obj != NULL) {
        if ((oflag & O_EXCL) != 0) {
            errno = EEXIST;
            return NULL;
        }

        if ((obj->flags & OBJ_FLAGS_EXCL) != 0) {
            errno = EACCES;
            return NULL;
        }

        if (obj_verify_protect(c, obj) == 0) {
            atomic_uint_inc(&obj->open_cnt);
            obj_unprotect(obj);

            return obj;
        }
    }

    /* if object not exist, try to create it */

    if ((oflag & O_CREAT) == 0) {
        errno = ENOENT;
        return NULL;
    }

    obj = __obj_alloc(c);

    if (unlikely(obj == NULL)) {
        return NULL;
    }

    /* eat mode, it is not supported now */

    (void)va_arg(valist, int);

    if (unlikely(__obj_init(c, obj, name, valist, 0) != 0)) {
        __obj_free(c, obj);

        obj = NULL;
    }

    if (unlikely(((oflag & O_EXCL) != 0) && (obj != NULL))) {
        obj->flags |= OBJ_FLAGS_EXCL;
    }

    return obj;
}

static int __obj_wait_unsafe(uintptr_t arg1, uintptr_t arg2)
{
    obj_id obj = (obj_id)arg1;

    if (unlikely(atomic_uint_get(&obj->safe_cnt) != 0)) {
        /* suspend and put current to "PEND" */

        obj->task_del = current;

        task_ready_q_del(current);

        current->status |= TASK_STATUS_PEND;
    }

    return 0;
}

/**
 * obj_destroy - destroy an object
 * @c:     the class this object should belongs to
 * @obj:   the object id
 *
 * return: 0 on success, negative value on error
 */
int obj_destroy(cls_id c, obj_id obj)
{
    if (unlikely(mutex_lock(&c->lock) != 0)) {
        return -1;
    }

    /*
     * objects can only be destroyed with c->lock locked, so obj_verify is
     * safe here
     */

    if (unlikely(obj_verify(c, obj) != 0)) {
        mutex_unlock(&c->lock);
        errno = ERRNO_OBJ_ILLEGAL_ID;
        return -1;
    }

    atomic_uint_dec(&obj->open_cnt);

    /*
     * prevent the object been opened by other task after the checking of
     * open_cnt, make the checking of open_cnt and `obj->magic = 0` atomic
     */

    task_lock();

    if (atomic_uint_get(&obj->open_cnt) != 0) {
        task_unlock();
        mutex_unlock(&c->lock);

        /*
         * set errno to ERRNO_OBJ_STILL_USING saying the object is not really
         * deleted as it is still been using
         */

        errno = ERRNO_OBJ_STILL_USING;
        return 0;
    }

    /* close the door, so any access to this object is denied */

    obj->magic = 0;

    task_unlock();

    do_critical(__obj_wait_unsafe, (uintptr_t)obj, 0);

    obj->task_del = NULL;

    /* delete object from class */

    dlist_del(&obj->node);

    mutex_unlock(&c->lock);

    if ((c->obj_ops->destroy != NULL) && unlikely(c->obj_ops->destroy(obj) != 0)) {
        WARN(
            "Fail to invoke c->obj_ops->destroy(%p), add the object back, but it may not works fine!",
            obj);

        mutex_lock(&c->lock);
        dlist_add(&c->objs, &obj->node);
        obj->magic = (uintptr_t)obj;
        atomic_uint_inc(&obj->open_cnt);
        mutex_unlock(&c->lock);

        return -1;
    }

    if ((obj->flags & OBJ_FLAGS_STATIC) == 0) {
        __obj_free(c, obj);
    }

    return 0;
}

/**
 * obj_foreach - iterate all objects in a class
 * @c:           the class id
 * @foreach_rtn: the callback function for each object
 * @arg1:        the argument#1 for the callback if any
 * @arg2:        ...
 *
 * return: object last reached or NULL if all object reached
 */
obj_id obj_foreach(cls_id c, obj_foreach_pfn foreach_rtn, ...)
{
    dlist_t *iter;
    int ret;
    va_list valist;
    obj_id last = NULL;

    if (unlikely(mutex_lock(&c->lock) != 0)) {
        return NULL;
    }

    dlist_foreach(iter, &c->objs)
    {
        obj_id obj = container_of(iter, obj_t, node);

        va_start(valist, foreach_rtn);

        ret = foreach_rtn(obj, valist);

        va_end(valist);

        if (unlikely(ret != 0)) {
            last = obj;

            break;
        }
    }

    (void)mutex_unlock(&c->lock);

    return last;
}

static int __obj_find(obj_id obj, va_list valist)
{
    const char *name = va_arg(valist, const char *);

    if (obj->name == NULL) {
        return 0;
    }

    /* return -1 so the iteration will stop and return this object */

    return strcmp(obj->name, (const char *)name) == 0 ? -1 : 0;
}

/**
 * obj_find - find object with a given name
 * @c:     the class id
 * @name:  the wanted object name
 *
 * return: the object id if found, NULL if not found
 */
obj_id obj_find(cls_id c, const char *name)
{
    return obj_foreach(c, __obj_find, name);
}
